home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / Other Langs / Tickle-4.0 (tcl) / tcl / expecTerm / command.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-11-17  |  46.0 KB  |  1,893 lines  |  [TEXT/MPS ]

  1. /* command.c - expect commands, except for interact and expect
  2. ***************************************************************************** 
  3. expecTerm version 1.0 beta
  4. Mark Weissman
  5. Christopher Matheus
  6. Copyright 1992 by GTE Laboratories Incorporated.
  7.  
  8. Portions of this work are in the public domain.  Permission to use,
  9. copy, modify, and distribute this software and its documentation for
  10. any purpose and without fee is hereby granted, provided that the above
  11. copyright notice appear in all copies and that both the copyright
  12. notice and warranty disclaimer appear in supporting documentation, and
  13. that the names of GTE Laboratories or any of their entities not be
  14. used in advertising or publicity pertaining to distribution of the
  15. software without specific, written prior permission.
  16.  
  17. GTE disclaims all warranties with regard to this software, including
  18. all implied warranties of merchantability and fitness for a particular
  19. purpose, even if GTE Laboratories Incorporated knows about the
  20. purpose.  In no event shall GTE be liable for any special, indirect or
  21. consequential damages or any damages whatsoever resulting from loss of
  22. use, data or profits, whether in an action of contract, negligence or
  23. other tortuous action, arising out of or in connection with the use or
  24. performance of this software.
  25.  
  26. This code is based on and may include parts of Don Libes' expect code:
  27.   expect written by: Don Libes, NIST, 2/6/90
  28.   Design and implementation of expect was paid for by U.S. tax
  29.   dollars.  Therefore it is public domain.  However, the author and NIST
  30.   would appreciate credit if this program or parts of it are used.
  31.  
  32. ******************************************************************************
  33.  
  34.  
  35. Written by: Don Libes, NIST, 2/6/90
  36. Modified by: Mark Weissman 9/92
  37.  
  38. Design and implementation of this program was paid for by U.S. tax
  39. dollars.  Therefore it is public domain.  However, the author and NIST
  40. would appreciate credit if this program or parts of it are used.
  41.  
  42. expect commands are as follows.  They are discussed further in the man page
  43. and the paper "expect: Curing Those Uncontrollable Fits of Interaction",
  44. Proceedings of the Summer 1990 USENIX Conference, Anaheim, California.
  45.  
  46. Command        Arguments    Returns            Sets
  47. -------        ---------    -------            ----
  48. close        [-i spawn_id]
  49. debug        [-f file] expr
  50. disconnect    [status]
  51. exit        [status]
  52. expect_after    patlst body ...
  53. expect_before    patlst body ...
  54. expect[_user]    patlst body ...    string matched        expect_status
  55. ftrace        level
  56. interact    str-body pairs    body return
  57. interpreter            TCL status
  58. log_file    [[-a] file]
  59. log_user    expr
  60. match_max    max match size
  61. overlay        [-] fd-spawn_id pairs
  62. ready        spawn_id set    spawn_ids ready
  63. send        [...]
  64. send_error    [...]
  65. send_user    [...]
  66. spawn        program [...]    pid            spawn_id
  67. system        shell command    TCL status
  68. trap        [[arg] siglist]
  69. wait                {pid status} or {-1 errno}
  70.  
  71. Variable of interest are:
  72.  
  73. Name        Type    Value            Set by        Default
  74. ----        ----    -----            ------        -------
  75. expect_match    str    string matched        expect cmds
  76. spawn_id    int    currently spawned proc    user/spawn cmd
  77. timeout        float    seconds            user        10
  78. user_spawn_id    int    spawn_id of user    expect itself    1
  79. send_slow    int/flt    send -s size/time    user
  80. send_human    5 flts    send -h timing        user
  81.  
  82. emulate        [-i spawn_id] [-term terminal_type] [-rows rows] [-cols cols]
  83.  
  84. */
  85.  
  86. #include <stdio.h>
  87. #include <sys/types.h>
  88. #include <sys/socket.h>
  89. #include <sys/time.h>
  90. #include <sys/ioctl.h>
  91. #include <fcntl.h>
  92. #include <sys/file.h>
  93. #include "expterm.h"
  94. #ifdef SYSV4
  95. #include <sys/stropts.h>
  96. #endif
  97. #ifndef M_XENIX
  98.  /* wait.h is not available on SCO XENIX 386. -- pf@artcom0.north.de */
  99. #if !defined(CRAY) || CRAY>=60
  100. #include <sys/wait.h>
  101. #endif
  102. #endif
  103. #include <varargs.h>
  104. #include <errno.h>
  105. #ifdef EXTERN_ERRNO
  106. extern int errno;
  107. #endif
  108. /*#include <memory.h> - deprecated - ANSI C moves them into string.h */
  109. #include <signal.h>
  110. #ifndef NO_STRING_H
  111. #include <string.h>
  112. #include <math.h>        /* for log/pow computation in send -h */
  113. #include <ctype.h>        /* all this for ispunct! */
  114. #endif
  115. #include <tcl.h>
  116. #include "translate.h"
  117. #include "global.h"
  118. #include "command.h"
  119.  
  120. #define SPAWN_ID_VARNAME "spawn_id"
  121.  
  122. int getptymaster();
  123. int getptyslave();
  124. extern exp_tty tty_current, tty_cooked;
  125.  
  126. int disconnected = FALSE;    /* whether we are a disconnected process */
  127. int forked = FALSE;        /* whether we are child process */
  128.  
  129. /* the following are just reserved addresses, to be used as ClientData */
  130. /* args to be used to tell commands how they were called. */
  131. /* The actual values won't be used, only the addresses, but I give them */
  132. /* values out of my irrational fear the compiler might collapse them all. */
  133. /* The first two are shared with expect.c */
  134. int expectCD_user = 0;        /* called as expect_user */
  135. int expectCD_process = 1;    /* called as expect */
  136. int sendCD_error = 2;    /* called as send_error */
  137. int sendCD_user = 3;    /* called as send_user */
  138. int sendCD_proc = 4;    /* called as send */
  139.  
  140. struct f *fs = 0;        /* process array (indexed by spawn_id's) */
  141. int fd_max = -1;        /* highest fd */
  142.  
  143. void usleep();
  144.  
  145. /* Do not terminate format strings with \n!!! */
  146. /*VARARGS*/
  147. void
  148. tcl_error(va_alist)
  149. va_dcl
  150. {
  151.     char *fmt;
  152.     va_list args;
  153.  
  154.     va_start(args);
  155.     fmt = va_arg(args,char *);
  156.     vsprintf(interp->result,fmt,args);
  157.     va_end(args);
  158. }
  159.  
  160. void
  161. flush_streams()
  162. {
  163.     fflush(stdout);
  164.     if (logfile) fflush(logfile);
  165.     if (debugfile) fflush(debugfile);
  166. }
  167.  
  168. /* returns f struct if valid or 0 */
  169. struct f *
  170. fd_to_f(fd,msg)
  171. int fd;
  172. char *msg;
  173. {
  174.     if (fd >= 0 && fd <= fd_max && (fs[fd].flags & FD_VALID)) {
  175.         return(fs+fd);
  176.     }
  177.  
  178.     tcl_error("%s: invalid spawn id (%d)",msg,fd);
  179.     return(0);
  180. }
  181.  
  182. /* returns fd or -1 if no such entry */
  183. int
  184. pid_to_fd(pid)
  185. int pid;
  186. {
  187.     int fd;
  188.  
  189.     for (fd=0;fd<=fd_max;fd++) {
  190.         if (fs[fd].pid == pid) return(fd);
  191.     }
  192.     return 0;
  193. }
  194.  
  195. int
  196. fd_close(fd)
  197. int fd;
  198. {
  199.     struct f *f = fd_to_f(fd,"close");
  200.     if (!f) return(TCL_ERROR);
  201.  
  202. /* Ignore close errors.  Some systems are really braindamaged and return */
  203. /* errors for no evident reason.  Anyway, receiving an error upon pty-close */
  204. /* doesn't mean anything anyway as far as I know. */
  205.     close(fd);
  206.     if (f->buffer) {
  207.         free(f->buffer);
  208.         f->buffer = 0;
  209.         f->msize = 0;
  210.         f->size = 0;
  211.         f->printed = 0;
  212.         free(f->lower);
  213.     }
  214.     if (SetSessionFromFD(fd)) DeleteSession();    /* MDW: Mon Jun 22 12:59:08 1992 */
  215.     if (f->flags & FD_USERWAITED) {
  216.       debuglog("fd_close(%d) f->flags: %x = 0\n", fd,f->flags);
  217.       f->flags = 0;
  218.     }
  219.     else {
  220.       debuglog("fd_close(%d) (f->flags: %x |= FD_CLOSED(%x)) = %x\n", fd, f->flags, FD_CLOSED, f->flags|FD_CLOSED);
  221.       f->flags |= FD_CLOSED;
  222.     }
  223.     return(TCL_OK);
  224. }
  225.  
  226. int
  227. fd_new(fd,pid)
  228. int fd;
  229. int pid;
  230. {
  231.     int i, low;
  232.     struct f *newfs;    /* temporary, so we don't lose old fs */
  233.  
  234.     /* resize table if nec */
  235.     if (fd > fd_max) {
  236.         if (!fs) {    /* no fd's yet allocated */
  237.             if (!(newfs = (struct f *)
  238.                 malloc(sizeof(struct f)*(fd+1)))) {
  239.                 /* malloc failed */
  240.                 return(TCL_ERROR);
  241.             }
  242.             low = 0;
  243.         } else {        /* enlarge fd table */
  244.             if (!(newfs = (struct f *)realloc((char *)fs,
  245.                 sizeof(struct f)*(fd+1)))) {
  246.                 /* malloc failed */
  247.                 return(TCL_ERROR);
  248.             }
  249.             low = fd_max+1;
  250.         }
  251.         fs = newfs;
  252.         fd_max = fd;
  253.         for (i = low; i <= fd_max; i++) { /* init new fd entries */
  254.             struct f *f;
  255.  
  256.             f = fs+i;
  257.             f->pid = 0;
  258.             f->buffer = 0;
  259.             f->lower = 0;
  260.             f->flags = FD_VALID;
  261.             f->msize = f->size = 0;
  262.         }
  263.     }
  264.     /* close down old table entry if nec */
  265.     fs[fd].pid = pid;        /* overwrite pid */
  266.     fs[fd].size = 0;        /* forget old data */
  267.                     /* but leave msize and buffer as is! */
  268.     fs[fd].printed = 0;
  269.     fs[fd].umsize = default_match_max;
  270.     fs[fd].flags = FD_VALID;    /* reset all flags */
  271.     fs[fd].quiet_start = fs[fd].quiet_end = 0.0;    /* MDW: Mon Jun 22 12:59:31 1992 */
  272.     return(TCL_OK);
  273. }
  274.  
  275. void
  276. init_spawn()
  277. {
  278.     Tcl_SetVar(interp,"user_spawn_id",USER_SPAWN_ID_LIT,0);
  279.  
  280.     fd_new(0,getpid());
  281.     fs[0].flags |= FD_USER;
  282. }
  283.  
  284. /* arguments are passed verbatim to execvp() */
  285. /*ARGSUSED*/
  286. static int
  287. cmdSpawn(clientData,interp,argc,argv)
  288. ClientData clientData;
  289. Tcl_Interp *interp;
  290. int argc;
  291. char **argv;
  292. {
  293.     int slave;
  294.     int pid;
  295.     char **a;
  296.     int ttyfd;
  297.     int master;
  298.     int pipep=0, sv[2];
  299.     extern char exp_pty_name[];
  300.  
  301.     if (argc == 1) {
  302.         tcl_error("usage: spawn [-pipe] program [args]");
  303.         return(TCL_ERROR);
  304.     }
  305.  
  306.     if (streq(argv[1], "-pipe")) pipep = 1;    /* MDW: Mon Jun 22 13:02:22 1992 */
  307.  
  308.     if (pipep) {    /* MDW: Mon Jun 22 13:02:12 1992 */
  309.       if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) {
  310.         tcl_error("SOCKETPAIR: too many programs spawned?");
  311.         return(TCL_ERROR);
  312.       }
  313.       else master = sv[0];
  314.     } else if (0 > (master = getptymaster())) {
  315.       tcl_error("too many programs spawned? - out of ptys");
  316.       return(TCL_ERROR);
  317.     }
  318.  
  319.     /* much easier to set this, than remember all masters */
  320.     fcntl(master,F_SETFD,1);    /* close on exec */
  321.  
  322.     for (a = argv;*a;a++) {
  323.         Log(0,"%s ",*a);
  324.     }
  325.     nflog("\r\n",0);
  326.  
  327.     if (pipep) ++argv;     /* MDW: Mon Jun 22 13:02:06 1992 */
  328.  
  329.     if (NULL == (argv[1] = Tcl_TildeSubst(interp,argv[1])))
  330.         return TCL_ERROR;
  331.  
  332.     flush_streams();
  333.  
  334.     if ((pid = fork()) == -1) {
  335.         tcl_error("fork: %s",sys_errlist[errno]);
  336.         return(TCL_ERROR);
  337.     }
  338.  
  339.     if (pid) {
  340.         /* parent */
  341.         char buf[3];
  342.         static char pidstr[32];
  343.  
  344.         if (pipep) close(sv[1]);    /* MDW: Mon Jun 22 13:03:02 1992 */
  345.  
  346.         if (TCL_ERROR == fd_new(master,pid)) return(TCL_ERROR);
  347.  
  348.         sprintf(pidstr,"%d",pid);
  349.         debuglog("spawn: returns {%s}\r\n",pidstr);
  350.         Tcl_Return(interp,pidstr,TCL_STATIC);
  351.  
  352.         /* tell user id of new process */
  353.         sprintf(buf,"%d",master);
  354.         Tcl_SetVar(interp,SPAWN_ID_VARNAME,buf,0);
  355.         return(TCL_OK);
  356.     }
  357.  
  358.     /* child process - do not return from here!  all errors must exit() */
  359.  
  360.     if (pipep) {    /* MDW: Mon Jun 22 13:04:03 1992 */
  361.       close(0);
  362.       close(1);
  363.       fcntl(sv[1],F_DUPFD,0);    /* duplicate 0 onto 2 */
  364.       fcntl(sv[1],F_DUPFD,1);
  365.       close(sv[1]);
  366.       slave=0;
  367.     }
  368.     else {
  369. #ifdef SYSV3
  370.       setpgrp();
  371. #else                /* !SYSV3 */
  372. #ifdef MIPS_BSD
  373.       /* required on BSD side of MIPS OS <jmsellen@watdragon.waterloo.edu> */
  374. #    include <sysv/sys.s>
  375.       syscall(SYS_setpgrp);
  376. #endif
  377.       setpgrp(0,getpid());    /* make a new pgrp leader */
  378.       ttyfd = open("/dev/tty", O_RDWR);
  379.       if (ttyfd >= 0) {
  380.         (void) ioctl(ttyfd, TIOCNOTTY, (char *)0);
  381.         (void) close(ttyfd);
  382.       }
  383. #endif                /* SYSV3 */
  384.  
  385.       close(0);
  386.       close(1);
  387.       /* leave 2 around awhile for stderr-related stuff */
  388.  
  389.       /* since we closed fd 0, open of pty slave must return fd 0 */
  390.  
  391.       /* since getptyslave may have to run stty, (some of which work on fd */
  392.       /* 0 and some of which work on 1) do the dup's inside getptyslave. */
  393.  
  394. #define STTY_INIT    "stty_init"
  395.       if (0 > (slave = getptyslave(get_var(STTY_INIT)))) {
  396.         errorlog("open(slave pty): %s\r\n",sys_errlist[errno]);
  397.         exit(-1);
  398.       }
  399.       /* sanity check */
  400.     }
  401.     if (slave != 0) {
  402.         errorlog("getptyslave: slave = %d but expected 0\n",slave);
  403.         return(-1);
  404.     }
  405.  
  406. #ifdef CRAY
  407.     setptyutmp();    /* create a utmp entry */
  408.     fixids();    /* so no more root access */
  409.  
  410.     /* _CRAY2 code from Hal Peterson <hrp@cray.com>, Cray Research, Inc. */
  411. #ifdef _CRAY2
  412.     /*
  413.      * Interpose a process between expect and the spawned child to
  414.      * keep the slave side of the pty open to allow time for expect
  415.      * to read the last output.  This is a workaround for an apparent
  416.      * bug in the Unicos pty driver on Cray-2's under Unicos 6.0 (at
  417.      * least).
  418.      */
  419.     if ((pid = fork()) == -1) {
  420.         errorlog("second fork: %s\r\n",sys_errlist[errno]);
  421.         exit(-1);
  422.     }
  423.  
  424.     if (pid) {
  425.          /* Intermediate process. */
  426.         int status;
  427.         double timeout;
  428.         char *t;
  429.         extern double atof();
  430.  
  431.         /* How long should we wait? */
  432.         if (t = get_var("pty_timeout"))
  433.           timeout = (double) atof(t);
  434.         else if (t = get_var("timeout"))
  435.           timeout = atof(t) / 2.0;
  436.         else
  437.             timeout = 5.0;
  438.  
  439.         /* Let the spawned process run to completion. */
  440.          while (wait(&status) < 0 && errno == EINTR)
  441.             /* empty body */;
  442.  
  443.         /* Wait for the pty to clear. */
  444.         usleep((unsigned)(timeout*(double)uS_PER_SEC));
  445.  
  446.         /* Duplicate the spawned process's status. */
  447.         if (WIFSIGNALED(status))
  448.             kill(getpid(), WTERMSIG(status));
  449.  
  450.         /* The kill may not have worked, but this will. */
  451.          exit(WEXITSTATUS(status));
  452.     }
  453. #endif /* _CRAY2 */
  454. #endif /* CRAY */
  455.  
  456.     /* by now, fd 0 and 1 point to slave pty, so fix 2 */
  457.     close(2);
  458.     fcntl(0,F_DUPFD,2);    /* duplicate 0 onto 2 */
  459.  
  460.     /* avoid fflush of cmdfile since this screws up the parents seek ptr */
  461.     /* There is no portable way to fclose a shared read-stream!!!! */
  462.     if (cmdfile && (cmdfile != stdin)) (void) close(fileno(cmdfile));
  463.     if (logfile) (void) fclose(logfile);
  464.     if (debugfile) (void) fclose(debugfile);
  465.     /* (possibly multiple) masters are closed automatically due to */
  466.     /* earlier fcntl(,,CLOSE_ON_EXEC); */
  467.  
  468.         (void) execvp(argv[1],argv+1);
  469.     /* Unfortunately, by now we've closed fd's to stderr, logfile and
  470.         debugfile.
  471.        The only reasonable thing to do is to send back the error as
  472.        part of the program output.  This will be picked up in an
  473.        expect or interact command.
  474.     */
  475.     errorlog("execvp(%s): %s\r\n",argv[1],sys_errlist[errno]);
  476.     exit(-1);
  477.     /*NOTREACHED*/
  478. }
  479.  
  480. /* returns current master (via out-parameter) */
  481. /* returns f or 0, but note that since fd_to_f calls tcl_error, this */
  482. /* may be immediately followed by a "return(TCL_ERROR)"!!! */
  483. struct f *
  484. update_master(m)
  485. int *m;
  486. {
  487.     char *s = get_var(SPAWN_ID_VARNAME);
  488.     *m = (s?atoi(s):USER_SPAWN_ID);
  489.     return(fd_to_f(*m,(s?s:USER_SPAWN_ID_LIT)));
  490. }
  491.  
  492. /*ARGSUSED*/
  493. static int
  494. cmdSystem(clientData, interp, argc, argv)
  495. ClientData clientData;
  496. Tcl_Interp *interp;
  497. int argc;
  498. char **argv;
  499. {
  500. #define MAX_ARGLIST 10240
  501.     int i;
  502.     char buf[MAX_ARGLIST];
  503.     char *bufp = buf;
  504.     int total_len = 0, arg_len;
  505.  
  506.     int stty_args_recognized = TRUE;
  507.     int cooked = FALSE;
  508.  
  509.     if (argc > 2 && streq(argv[1],"stty")) {
  510.         extern int ioctled_devtty;
  511.         ioctled_devtty = TRUE;
  512.  
  513.         for (i=2;i<argc;i++) {
  514.             if (streq(argv[i],"raw") ||
  515.                 streq(argv[i],"-cooked")) {
  516.                 tty_raw(1);
  517.             } else if (streq(argv[i],"-raw") ||
  518.                    streq(argv[i],"cooked")) {
  519.                 cooked = TRUE;
  520.                 tty_raw(-1);
  521.             } else if (streq(argv[i],"echo")) {
  522.                 tty_echo(1);
  523.             } else if (streq(argv[i],"-echo")) {
  524.                 tty_echo(-1);
  525.             } else stty_args_recognized = FALSE;
  526.         }
  527.         /* if unknown args, fall thru and let real stty have a go */
  528.         if (stty_args_recognized) {
  529. #ifdef POSIX
  530.              if (tcsetattr(0, TCSADRAIN, &tty_current) == -1) {
  531. #else
  532. #    ifdef SYSV3
  533.                 if (ioctl(0, TCSETAW, &tty_current) == -1) {
  534. #    else
  535.             if (ioctl(0, TIOCSETP, &tty_current) == -1) {
  536. #    endif
  537. #endif
  538.                 errorlog("ioctl(user): %s\r\n",sys_errlist[errno]);
  539.                 bye(-1);
  540.             }
  541.             return(TCL_OK);
  542.         }
  543.     }
  544.  
  545.     for (i = 1;i<argc;i++) {
  546.         total_len += (1 + (arg_len = strlen(argv[i])));
  547.         if (total_len > MAX_ARGLIST) {
  548.             tcl_error("system: args too long (>=%d chars)",
  549.                 total_len);
  550.             return(TCL_ERROR);
  551.         }
  552.         memcpy(bufp,argv[i],arg_len);
  553.         bufp += arg_len;
  554.         /* no need to check bounds, we accted for it earlier */
  555.         memcpy(bufp," ",1);
  556.         bufp += 1;
  557.     }
  558.  
  559.     *(bufp-1) = '\0';
  560.     i = system(buf);
  561.     debuglog("system(%s) = %d\r\n",buf,i);
  562.  
  563.     if (!stty_args_recognized) {
  564.         /* find out what weird options user asked for */
  565. #ifdef POSIX
  566.              if (tcgetattr(0, &tty_current) == -1) {
  567. #else
  568. #    ifdef SYSV3
  569.             if (ioctl(0, TCGETA, &tty_current) == -1) {
  570. #    else
  571.         if (ioctl(0, TIOCGETP, &tty_current) == -1) {
  572. #    endif
  573. #endif
  574.             errorlog("ioctl(get): %s\r\n",sys_errlist[errno]);
  575.             bye(-1);
  576.         }
  577.         if (cooked) {
  578.             /* find out user's new defn of 'cooked' */
  579.             tty_cooked = tty_current;
  580.         }
  581.     }
  582.  
  583.     /* Following the style of Tcl_ExecCmd, we can just return the */
  584.     /* raw result (appropriately shifted and masked) to Tcl */
  585.     return(0xff & (i>>8));
  586. }
  587.  
  588. /* CRAY code from Hal Peterson <hrp@cray.com>, Cray Research, Inc. */
  589. /* To quote Hal, "expect runs suid, then swaps the UIDs around, then
  590. runs an suid program, say rsh.  What happens?  In expect, I (hrp) am
  591. in the effective UID and root is in the real UID, and when I run rsh
  592. root overwrites the effective user ID, so rsh runs with both its real
  593. and effective user IDs set to 0, which isn't what I want.  Also, it
  594. would be simple to write a C program which, when exec'd from an SUID
  595. expect, would give me a root shell by swapping the UIDs.  The changes
  596. I made to create cmdExec from Tcl_ExecCmd eliminate these problems, at
  597. the expense of duplicating a bit of code." */
  598.  
  599. #ifdef CRAY
  600. /* run the Tcl "exec" builtin, but first fix the user IDs. */
  601. /*ARGSUSED*/
  602. static int
  603. cmdExec(clientData, interp, argc, argv)
  604. ClientData clientData;
  605. Tcl_Interp *interp;
  606. int argc;
  607. char **argv;
  608. {
  609.     char *input = "";            /* Points to the input remaining to
  610.                      * send to the child process. */
  611.     int inputSize;            /* # of bytes of input. */
  612. #define MAX_PIPE_INPUT 4095
  613. #define TMP_STDIN_NAME "/tmp/tcl.in.XXXXXX"
  614. #define TMP_STDOUT_NAME "/tmp/tcl.out.XXXXXX"
  615.     char inName[sizeof(TMP_STDIN_NAME) + 1];
  616.     char outName[sizeof(TMP_STDOUT_NAME) + 1];
  617.     int stdIn, stdOut, result, i;
  618.     int pid = -1;            /* -1 means child process doesn't
  619.                      * exist (yet).  Non-zero gives its
  620.                      * id (0 only in child). */
  621. #ifdef _BSD
  622.     union wait status;
  623. #else
  624.     int status;
  625. #endif
  626.     char *cmdName, *execName;
  627.  
  628.     /*
  629.      * Look through the arguments for a standard input specification
  630.      * ("< value" in two arguments).  If found, collapse it out.
  631.      * Shuffle all the arguments back over the "exec" argument, so that
  632.      * there's room for a NULL argument at the end.
  633.      */
  634.  
  635.     cmdName = argv[0];
  636.     for (i = 1; i < argc; i++) {
  637.     argv[i-1] = argv[i];
  638.     if ((argv[i][0] != '<') || (argv[i][1] != 0)) {
  639.         continue;
  640.     }
  641.     i++;
  642.     if (i >= argc) {
  643.         sprintf(interp->result,
  644.             "specified \"<\" but no input in \"%.50s\" command",
  645.             cmdName);
  646.         return TCL_ERROR;
  647.     }
  648.     input = argv[i];
  649.     for (i++; i < argc; i++) {
  650.         argv[i-3] = argv[i];
  651.     }
  652.     argc -= 2;
  653.     }
  654.  
  655.     argc -= 1;            /* Drop "exec" argument. */
  656.     argv[argc] = NULL;
  657.     if (argc < 1) {
  658.     sprintf(interp->result,
  659.         "wrong # args:  should be \"%.50s command [arg arg ...]\"",
  660.         cmdName);
  661.     return TCL_ERROR;
  662.     }
  663.     execName = Tcl_TildeSubst(interp, argv[0]);
  664.     if (execName == NULL) {
  665.     return TCL_ERROR;
  666.     }
  667.  
  668.     /*
  669.      * Set up input and output files for the child.
  670.      */
  671.  
  672.     stdIn = stdOut = -1;
  673.     inputSize = strlen(input);
  674.     if (inputSize > 0) {
  675.     strcpy(inName, TMP_STDIN_NAME);
  676.     mktemp(inName);
  677.     stdIn = open(inName, O_RDWR|O_CREAT, 0);
  678.     if (stdIn < 0) {
  679.         sprintf(interp->result,
  680.             "couldn't create input file for \"%.50s\" command: %.50s",
  681.             cmdName, strerror(errno));
  682.         result = TCL_ERROR;
  683.         goto cleanup;
  684.     }
  685.     if (write(stdIn, input, inputSize) != inputSize) {
  686.         sprintf(interp->result,
  687.             "couldn't write file input for command: %.50s",
  688.             strerror(errno));
  689.         result = TCL_ERROR;
  690.         goto cleanup;
  691.     }
  692.     if ((lseek(stdIn, 0L, 0) == -1) || (unlink(inName) == -1)) {
  693.         sprintf(interp->result,
  694.             "couldn't reset or remove input file for command: %.50s",
  695.             strerror(errno));
  696.         result = TCL_ERROR;
  697.         goto cleanup;
  698.     }
  699.     }
  700.  
  701.     strcpy(outName, TMP_STDOUT_NAME);
  702.     mktemp(outName);
  703.     stdOut = open(outName, O_RDWR|O_CREAT, 0);
  704.     if (stdOut < 0) {
  705.     sprintf(interp->result,
  706.         "couldn't create output file for \"%.50s\" command: %.50s",
  707.         cmdName, strerror(errno));
  708.     result = TCL_ERROR;
  709.     goto cleanup;
  710.     }
  711.     if (unlink(outName) == -1) {
  712.     sprintf(interp->result,
  713.         "couldn't  remove output file for command: %.50s",
  714.         strerror(errno));
  715.     result = TCL_ERROR;
  716.     goto cleanup;
  717.     }
  718.  
  719.     /*
  720.      * Fork off the child process.
  721.      */
  722.  
  723. #ifdef _BSD
  724.     pid = vfork();
  725. #else
  726.     pid = fork();
  727. #endif
  728.     if (pid == -1) {
  729.     sprintf(interp->result,
  730.         "couldn't fork child for \"%.50s\" command: %.50s",
  731.         cmdName, strerror(errno));
  732.     result = TCL_ERROR;
  733.     goto cleanup;
  734.     }
  735.     if (pid == 0) {
  736.     char errSpace[100];
  737.  
  738.     if (((stdIn != -1) && (dup2(stdIn, 0) == -1))
  739.         || (dup2(stdOut, 1) == -1) || (dup2(stdOut, 2) == -1)) {
  740.         char *err;
  741.         err = "forked process couldn't set up input/output";
  742.         write(stdOut, err, strlen(err));
  743.         _exit(1);
  744.     }
  745.     for (i = 3; i <= stdOut; i++) {
  746.         close(i);
  747.     }
  748.     fixids();
  749.     execvp(execName, argv);
  750.     sprintf(errSpace, "couldn't find a \"%.50s\" to execute", argv[0]);
  751.     write(1, errSpace, strlen(errSpace));
  752.     _exit(1);
  753.     }
  754.  
  755.     /*
  756.      * In the parent, wait for the child to exit.
  757.      */
  758.  
  759.     while (1) {
  760.     int child;
  761.  
  762.     child = wait(&status);
  763.     if (child == -1) {
  764.         sprintf(interp->result,
  765.             "child process disappeared mysteriously");
  766.         result = TCL_ERROR;
  767.         goto cleanup;
  768.     }
  769.     if (child == pid) {
  770.         if (!WIFEXITED(status)) {
  771.         sprintf(interp->result, "command terminated abnormally");
  772.         result = TCL_ERROR;
  773.         goto cleanup;
  774.         }
  775.         result = WEXITSTATUS(status);
  776.         break;
  777.     }
  778.     }
  779.  
  780.     /*
  781.      * Read the child's output and put it into our result.
  782.      */
  783.  
  784.     if (lseek(stdOut, 0L, 0) == -1) {
  785.     sprintf(interp->result,
  786.         "couldn't rewind output file for command: %.50s",
  787.         strerror(errno));
  788.     result = TCL_ERROR;
  789.     goto cleanup;
  790.     }
  791.     while (1) {
  792. #define BUFFER_SIZE 1000
  793.     char buffer[BUFFER_SIZE+1];
  794.     int count;
  795.  
  796.     count = read(stdOut, buffer, BUFFER_SIZE);
  797.  
  798.     if (count == 0) {
  799.         break;
  800.     }
  801.     if (count < 0) {
  802.         sprintf(interp->result,
  803.             "error reading output file for command: %.50s",
  804.             strerror(errno));
  805.         result = TCL_ERROR;
  806.         goto cleanup;
  807.     }
  808.     buffer[count] = 0;
  809.     Tcl_AppendResult(interp, buffer, (char *) NULL);
  810.     }
  811.  
  812.     /*
  813.      * Cleanup anything that's left.  We could get here from many
  814.      * different points, depending on whether (and where) an error
  815.      * occurred.
  816.      */
  817.  
  818.     cleanup:
  819.     if (stdIn != -1) {
  820.     close(stdIn);
  821.     }
  822.     if (stdOut != -1) {
  823.     close(stdOut);
  824.     }
  825.     return result;
  826. }
  827. #endif /* CRAY */
  828.  
  829. static int SendDirectP = 0;
  830. /* write exactly this many bytes, i.e. retry partial writes */
  831. /* I don't know if this is necessary, but large sends might have this */
  832. /* problem */
  833. /* returns 0 for success, -1 for failure */
  834. int
  835. exact_write(fd,buffer,rembytes)
  836. int fd;
  837. char *buffer;
  838. int rembytes;
  839. {
  840.     int cc;
  841.  
  842.     while (rembytes) {
  843.         if (-1 == (cc = (SendDirectP ? write(fd,buffer,rembytes) : Write(fd,buffer,rembytes))))
  844.           return(-1);
  845.         /* if (0 == cc) return(-1); can't happen! */
  846.  
  847.         buffer += cc;
  848.         rembytes -= cc;
  849.     }
  850.     return(0);
  851. }
  852.  
  853. struct slow_arg {
  854.     int size;
  855.     long time;        /* microseconds */
  856. };
  857.  
  858. /* returns -1 failure, 0 if successful */
  859. int
  860. get_slow_args(x)
  861. struct slow_arg *x;
  862. {
  863.     float ftime;
  864.  
  865.     int sc;        /* return from scanf */
  866.     char *s = get_var("send_slow");
  867.     if (!s) {
  868.         tcl_error("send -s: send_slow has no value");
  869.         return(-1);
  870.     }
  871.     if (2 != (sc = sscanf(s,"%d %f",&x->size,&ftime))) {
  872.         tcl_error("send -s: found %d value(s) in send_slow but need 2",sc);
  873.         return(-1);
  874.     }
  875.     if (x->size <= 0) {
  876.         tcl_error("send -s: size (%d) in send_slow must be positive", x->size);
  877.         return(-1);
  878.     }
  879.     x->time = ftime*1000000L;
  880.     if (x->time <= 0) {
  881.         tcl_error("send -s: time (%f) in send_slow must be larger",ftime);
  882.         return(-1);
  883.     }
  884.     return(0);
  885. }
  886.  
  887. /* returns 0 for success, -1 for failure */
  888. int
  889. slow_write(fd,buffer,rembytes,arg)
  890. int fd;
  891. char *buffer;
  892. int rembytes;
  893. struct slow_arg *arg;
  894. {
  895.     while (rembytes > 0) {
  896.         int len;
  897.  
  898.         len = (arg->size<rembytes?arg->size:rembytes);
  899.         if (0 > exact_write(fd,buffer,len)) return(-1);
  900.         rembytes -= arg->size;
  901.         buffer += arg->size;
  902.  
  903.         /* skip sleep after last write */
  904.         if (rembytes > 0) usleep(arg->time);
  905.     }
  906.     return(0);
  907. }
  908.  
  909. struct human_arg {
  910.     float alpha;        /* average interarrival time in seconds */
  911.     float alpha_eow;    /* as above but for eow transitions */
  912.     float c;        /* shape */
  913.     float min, max;
  914. };
  915.  
  916. /* returns -1 if error, 0 if success */
  917. int
  918. get_human_args(x)
  919. struct human_arg *x;
  920. {
  921.     int sc;        /* return from scanf */
  922.     char *s = get_var("send_human");
  923.  
  924.     if (!s) {
  925.         tcl_error("send -h: send_human has no value");
  926.         return(-1);
  927.     }
  928.     if (5 != (sc = sscanf(s,"%f %f %f %f %f",
  929.             &x->alpha,&x->alpha_eow,&x->c,&x->min,&x->max))) {
  930.         tcl_error("send -h: found %d value(s) in send_human but need 5",sc);
  931.         return(-1);
  932.     }
  933.     if (x->alpha < 0 || x->alpha_eow < 0) {
  934.         tcl_error("send -h: average interarrival times (%f %f) must be non-negative in send_human", x->alpha,x->alpha_eow);
  935.         return(-1);
  936.     }
  937.     if (x->c <= 0) {
  938.         tcl_error("send -h: variability (%f) in send_human must be positive",x->c);
  939.         return(-1);
  940.     }
  941.     x->c = 1/x->c;
  942.  
  943.     if (x->min < 0) {
  944.         tcl_error("send -h: minimum (%f) in send_human must be non-negative",x->min);
  945.         return(-1);
  946.     }
  947.     if (x->max < 0) {
  948.         tcl_error("send -h: maximum (%f) in send_human must be non-negative",x->max);
  949.         return(-1);
  950.     }
  951.     if (x->max < x->min) {
  952.         tcl_error("send -h: maximum (%f) must be >= minimum (%f) in send_human",x->max,x->min);
  953.         return(-1);
  954.     }
  955.     return(0);
  956. }
  957.  
  958. /* This function is my implementation of the Weibull distribution. */
  959. /* I've added a max time and an "alpha_eow" that captures the slight */
  960. /* but noticable change in human typists when hitting end-of-word */
  961. /* transitions. */
  962. /* returns 0 for success, -1 for failure */
  963. int
  964. human_write(fd,buffer,arg)
  965. int fd;
  966. char *buffer;
  967. struct human_arg *arg;
  968. {
  969.     char *sp;
  970.     float t, unit_random();
  971.     float alpha;
  972.     int wc;
  973.     int in_word = TRUE;
  974.  
  975.     debuglog("human_write: avg_arr=%f/%f  1/shape=%f  min=%f  max=%f\r\n",
  976.         arg->alpha,arg->alpha_eow,arg->c,arg->min,arg->max);
  977.  
  978.     for (sp = buffer;*sp;sp++) {
  979.         /* use the end-of-word alpha at eow transitions */
  980.         if (in_word && (ispunct(*sp) || isspace(*sp)))
  981.             alpha = arg->alpha_eow;
  982.         else alpha = arg->alpha;
  983.         in_word = !(ispunct(*sp) || isspace(*sp));
  984.  
  985.         t = (pow(-log((double)unit_random()),arg->c)*alpha)+arg->min;
  986.         if (t>arg->max) t = arg->max;
  987.  
  988.         /* skip sleep before writing first character */
  989.         if (sp != buffer) usleep((long)(t*1000000));
  990.  
  991.         wc = Write(fd,sp,1);
  992.         if (0 > wc) return(wc);
  993.     }
  994.     return(0);
  995. }
  996.  
  997. /* take strings with newlines and insert carriage-returns.  This allows user */
  998. /* to write send_user strings without always putting in \r. */
  999. /* If len == 0, use strlen to compute it */
  1000. /* NB: if terminal is not in raw mode, nothing is done. */
  1001. char *
  1002. cook(s,len)
  1003. char *s;
  1004. int *len;    /* current and new length of s */
  1005. {
  1006.     static int destlen = 0;
  1007.     static char *dest = 0;
  1008.     char *d;        /* ptr into dest */
  1009.     unsigned int need;
  1010.     extern int is_raw;
  1011.  
  1012.     if (s == 0) return("<null>");
  1013.  
  1014.     if (!is_raw) return(s);
  1015.  
  1016.     /* worst case is every character takes 2 to represent */
  1017.     need = 1 + 2*(len?*len:strlen(s));
  1018.     if (need > destlen) {
  1019.         if (dest) free(dest);
  1020.         if (!(dest = (char *)malloc(need))) {
  1021.             destlen = 0;
  1022.             return("malloc failed in cook");
  1023.         }
  1024.         destlen = need;
  1025.     }
  1026.  
  1027.     for (d = dest;*s;s++) {
  1028.         if (*s == '\n') {
  1029.             *d++ = '\r';
  1030.             *d++ = '\n';
  1031.         } else {
  1032.             *d++ = *s;
  1033.         }
  1034.     }
  1035.     *d = '\0';
  1036.     if (len) *len = d-dest;
  1037.     return(dest);
  1038. }
  1039.  
  1040. /* I've rewritten this to be unbuffered.  I did this so you could shove */
  1041. /* large files through "send".  If you are concerned about efficiency */
  1042. /* you should quote all your send args to make them one single argument. */
  1043. /*ARGSUSED*/
  1044. static int
  1045. cmdSend(clientData, interp, argc, argv)
  1046. ClientData clientData;
  1047. Tcl_Interp *interp;
  1048. int argc;
  1049. char **argv;
  1050. {
  1051.     int master = -1;
  1052.     int master_flag = FALSE;
  1053.     int i;
  1054.     int wc = 0;    /* if negative, a write has failed */
  1055.     extern int is_debugging;
  1056.     struct human_arg human_args;
  1057.     struct slow_arg slow_args;
  1058. #define SEND_STYLE_PLAIN 0
  1059. #define SEND_STYLE_HUMAN 1
  1060. #define SEND_STYLE_SLOW 2
  1061.     int send_style = SEND_STYLE_PLAIN;
  1062.     int want_cooked = TRUE;
  1063.     int send_direct = SendDirectP;
  1064.     argv++;
  1065.     argc--;
  1066.     while (argc) {
  1067.         if (streq(*argv,"-i")) {
  1068.             argc--; argv++;
  1069.             if (argc==0) {
  1070.                 tcl_error("-i requires following spawn_id");
  1071.                 return(TCL_ERROR);
  1072.             }
  1073.             master = atoi(*argv);
  1074.             master_flag = TRUE;
  1075.             argc--; argv++;
  1076.             continue;
  1077.         } else if (streq(*argv,"-direct")) {
  1078.             argc--; argv++;
  1079.             send_direct = 1;
  1080.         } else if (streq(*argv,"-h")) {
  1081.             argc--; argv++;
  1082.             if (-1 == get_human_args(&human_args)) return(TCL_ERROR);
  1083.             send_style = SEND_STYLE_HUMAN;
  1084.         } else if (streq(*argv,"-s")) {
  1085.             argc--; argv++;
  1086.             if (-1 == get_slow_args(&slow_args)) return(TCL_ERROR);
  1087.             send_style = SEND_STYLE_SLOW;
  1088.         } else if (streq(*argv,"-raw")) {
  1089.             argc--; argv++;
  1090.             want_cooked = FALSE;
  1091.         } else break;
  1092.     }
  1093.  
  1094.     if (clientData == &sendCD_user) master = 1;
  1095.     else if (clientData == &sendCD_error) master = 2;
  1096.     else if (!master_flag) {
  1097.         if (0 == update_master(&master)) return(TCL_ERROR);
  1098.         /* true if called as Send with user_spawn_id */
  1099.         if (is_user(master)) {
  1100.             clientData = &sendCD_user;
  1101.             master = 1;
  1102.         }
  1103.     }
  1104.  
  1105. #define send_to_stdout    (clientData == &sendCD_user)
  1106. #define send_to_stderr    (clientData == &sendCD_error)
  1107. #define send_to_proc    (clientData == &sendCD_proc)
  1108.  
  1109. /*debuglog("master = %d  send_to_proc = %d\r\n",master,send_to_proc);*/
  1110.  
  1111.     if (send_to_proc) want_cooked = FALSE;
  1112.     if (send_to_proc) debuglog("send: sent {");
  1113.     /* if the closing brace doesn't appear, that's because an error was */
  1114.     /* encountered before we could send it */
  1115.  
  1116.     for (i = 0;i<argc;i++) {
  1117.         if (i != 0) {
  1118.             /* wedge ' ' between other args.  If you don't want */
  1119.             /* this, make it all into one arg! */
  1120.             if (send_to_proc) {
  1121.                 debuglog(" ");
  1122.             } else {
  1123. /*                if (send_to_stderr) fwrite(" ",1,1,stderr);*/
  1124.                 if (debugfile) fwrite(" ",1,1,debugfile);
  1125.                 if ((send_to_stdout && logfile_all) || logfile)
  1126.                     fwrite(" ",1,1,logfile);
  1127.             }
  1128.             SendDirectP = send_direct;
  1129.             switch (send_style) {
  1130.             case SEND_STYLE_SLOW:
  1131.                 wc = slow_write(master," ",1,&slow_args);
  1132.                 break;
  1133.             case SEND_STYLE_HUMAN:
  1134.                 wc = human_write(master," ",&human_args);
  1135.                 break;
  1136.             default:
  1137.                 wc = exact_write(master," ",1);
  1138.                 break;
  1139.             }
  1140.             SendDirectP = 0;
  1141.             
  1142.         }
  1143.         if (wc >= 0) {
  1144.             int len = strlen(argv[i]);
  1145.             if (send_to_proc) {
  1146.                 debuglog("%s",dprintify(argv[i]));
  1147.             } else {
  1148. /*                if (send_to_stderr)
  1149.                     fwrite(argv[i],1,len,stderr);*/
  1150.                 if (debugfile)
  1151.                     fwrite(argv[i],1,len,debugfile);
  1152.                 if ((send_to_stdout && logfile_all) || logfile)
  1153.                     fwrite(argv[i],1,len,logfile);
  1154.             }
  1155.  
  1156.             if (want_cooked) argv[i] = cook(argv[i],&len);
  1157.  
  1158.             SendDirectP = send_direct;
  1159.             switch (send_style) {
  1160.             case SEND_STYLE_SLOW:
  1161.                 wc = slow_write(master,argv[i],len,&slow_args);
  1162.                 break;
  1163.             case SEND_STYLE_HUMAN:
  1164.                 wc = human_write(master,argv[i],&human_args);
  1165.                 break;
  1166.             default:
  1167.                 wc = exact_write(master,argv[i],len);
  1168.                 break;
  1169.             }
  1170.             SendDirectP = 0;
  1171.         }
  1172.         if (wc < 0) {
  1173.             tcl_error("write(spawn_id=%d): %s",
  1174.                         master,sys_errlist[errno]);
  1175.             if (send_to_proc) fd_close(master);
  1176.             return(TCL_ERROR);
  1177.         }
  1178.     }
  1179.     if (send_to_proc) debuglog("} to spawn id %d\r\n",master);
  1180.     else {
  1181.         /* not really necessary, but makes the debug log */
  1182.         /* look more rational */
  1183.         flush_streams();
  1184.     }
  1185.  
  1186.     return(TCL_OK);
  1187. }
  1188.  
  1189. void
  1190. cmdLogFile_usage() {
  1191.     tcl_error("usage: log_file [[-a] file]");
  1192. }
  1193.  
  1194. /*ARGSUSED*/
  1195. static int
  1196. cmdLogFile(clientData, interp, argc, argv)
  1197. ClientData clientData;
  1198. Tcl_Interp *interp;
  1199. int argc;
  1200. char **argv;
  1201. {
  1202.     /* when this function returns, we guarantee that if logfile_all */
  1203.     /* is TRUE, then logfile is non-zero */
  1204.  
  1205.     logfile_all = FALSE;
  1206.  
  1207.     argv++; argc--;
  1208.     while (argc) {
  1209.         if (0 != strcmp(*argv,"-a")) break;
  1210.         argc--;argv++;
  1211.         logfile_all = TRUE;
  1212.     }
  1213.  
  1214.     /* note that logfile_all may be TRUE here, even if logfile is zero */
  1215.  
  1216.     if (argc > 1) {
  1217.         /* too many arguments */
  1218.         cmdLogFile_usage();
  1219.         if (!logfile) logfile_all = FALSE;
  1220.         return(TCL_ERROR);
  1221.     }
  1222.  
  1223.     if (argc == 0) {
  1224.         if (logfile_all) {
  1225.             cmdLogFile_usage();
  1226.             if (!logfile) logfile_all = FALSE;
  1227.             return(TCL_ERROR);
  1228.         } else if (logfile) {
  1229.             fclose(logfile);
  1230.             logfile = 0;
  1231.         } else {
  1232.             /* asked to close file but not open, ignore */
  1233.             /* tcl_error("log not open"); */
  1234.             /* return(TCL_ERROR); */
  1235.         }
  1236.     } else {
  1237.         if (logfile) fclose(logfile);
  1238.         if (*argv[0] == '~') {
  1239.             argv[0] = Tcl_TildeSubst(interp, argv[0]);
  1240.             if (argv[0] == NULL) return(TCL_ERROR);
  1241.         }
  1242.  
  1243.         if (NULL == (logfile = fopen(argv[0],"a"))) {
  1244.             tcl_error("%s: %s",argv[0],sys_errlist[errno]);
  1245.             logfile_all = FALSE;
  1246.             return(TCL_ERROR);
  1247.         }
  1248.     }
  1249.     return(TCL_OK);
  1250. }
  1251.  
  1252. /*ARGSUSED*/
  1253. static int
  1254. cmdLogUser(clientData, interp, argc, argv)
  1255. ClientData clientData;
  1256. Tcl_Interp *interp;
  1257. int argc;
  1258. char **argv;
  1259. {
  1260.     if (argc != 2) {
  1261.         tcl_error("usage: log_user expr");
  1262.         return(TCL_ERROR);
  1263.     }
  1264.  
  1265.     if (0 == atoi(argv[1])) loguser = FALSE;
  1266.     else loguser = TRUE;
  1267.     return(TCL_OK);
  1268. }
  1269.  
  1270. void
  1271. cmdDebug_usage()
  1272. {
  1273.     tcl_error("usage: debug [-f file] expr");
  1274. }
  1275.  
  1276. /*ARGSUSED*/
  1277. static int
  1278. cmdDebug(clientData, interp, argc, argv)
  1279. ClientData clientData;
  1280. Tcl_Interp *interp;
  1281. int argc;
  1282. char **argv;
  1283. {
  1284.     extern int is_debugging;
  1285.     int fopened = FALSE;
  1286.  
  1287.     argv++;
  1288.     argc--;
  1289.     while (argc) {
  1290.         if (!streq(*argv,"-f")) break;
  1291.         argc--;argv++;
  1292.         if (argc < 1) {
  1293.             cmdDebug_usage();
  1294.             return(TCL_ERROR);
  1295.         }
  1296.         if (debugfile) fclose(debugfile);
  1297.         if (*argv[0] == '~') {
  1298.             argv[0] = Tcl_TildeSubst(interp, argv[0]);
  1299.             if (argv[0] == NULL) return(TCL_ERROR);
  1300.         }
  1301.         if (NULL == (debugfile = fopen(*argv,"a"))) {
  1302.             tcl_error("%s: %s",*argv,sys_errlist[errno]);
  1303.             return(TCL_ERROR);
  1304.         }
  1305.         fopened = TRUE;
  1306.         argc--;argv++;
  1307.     }
  1308.  
  1309.     if (argc != 1) {
  1310.         cmdDebug_usage();
  1311.         return(TCL_ERROR);
  1312.     }
  1313.  
  1314.     /* if no -f given, close file */
  1315.     if (fopened == FALSE && debugfile) {
  1316.         fclose(debugfile);
  1317.         debugfile = 0;
  1318.     }
  1319.  
  1320.     is_debugging = atoi(*argv);
  1321.     return(TCL_OK);
  1322. }
  1323.  
  1324. /*ARGSUSED*/
  1325. static int
  1326. cmdExit(clientData, interp, argc, argv)
  1327. ClientData clientData;
  1328. Tcl_Interp *interp;
  1329. int argc;
  1330. char **argv;
  1331. {
  1332.     int value = 0;
  1333.  
  1334.     if (argc > 2) {
  1335.         tcl_error("usage: exit [status]");
  1336.         return(TCL_ERROR);
  1337.     }
  1338.  
  1339.     if (argc == 2) {
  1340.         if (Tcl_GetInt(interp, argv[1], &value) != TCL_OK) {
  1341.             return TCL_ERROR;
  1342.         }
  1343.     }
  1344.     bye(value);
  1345.     /*NOTREACHED*/
  1346. }
  1347.  
  1348. /*ARGSUSED*/
  1349. static int
  1350. cmdClose(clientData, interp, argc, argv)
  1351. ClientData clientData;
  1352. Tcl_Interp *interp;
  1353. int argc;
  1354. char **argv;
  1355. {
  1356.     int m = -1;
  1357.  
  1358.     if (argc == 1) {
  1359.         if (update_master(&m) == 0) return(TCL_ERROR);
  1360.         else if (m <= 2) {    /* MDW: Mon Jun 22 13:10:55 1992 */
  1361.           sprintf(interp->result, "No Master for spawn_id: %d", m);
  1362.           return(TCL_ERROR);
  1363.         }
  1364.     } else if (streq(argv[1],"-i")) {
  1365.         if (argc != 3) {
  1366.             tcl_error("usage: close [-i spawn_id]");
  1367.             return(TCL_ERROR);
  1368.         }
  1369.         m = atoi(argv[2]);
  1370.     }
  1371.  
  1372.     if (m != -1) return(fd_close(m));
  1373.  
  1374.     /* doesn't look like our format, it must be a Tcl-style file handle */
  1375.     /* we're so lucky that the formats are easily distinguishable */
  1376.     /* Historical note: we used "close" in Tcl long before there was a */
  1377.     /* builtin by the same name. */
  1378.  
  1379.     /* don't know what is formally correct as 1st arg, but I see the */
  1380.     /* code doesn't use it anyway */
  1381.     return(Tcl_CloseCmd(clientData,interp,argc,argv));
  1382. }
  1383.  
  1384. /*ARGSUSED*/
  1385. void
  1386. tcl_tracer(clientData,interp,level,command,cmdProc,cmdClientData,argc,argv)
  1387. ClientData clientData;
  1388. Tcl_Interp *interp;
  1389. int level;
  1390. char *command;
  1391. int (*cmdProc)();
  1392. ClientData cmdClientData;
  1393. int argc;
  1394. char *argv[];
  1395. {
  1396.     int i;
  1397.  
  1398.     /* come out on stderr, by using errorlog */
  1399.     errorlog("%2d",level);
  1400.     for (i = 0;i<level;i++) nferrorlog("  ",0/*ignored - satisfy lint*/);
  1401.     errorlog("%s\r\n",command);
  1402.     flush_streams();
  1403. }
  1404.  
  1405. /*ARGSUSED*/
  1406. static int
  1407. cmdTrace(clientData, interp, argc, argv)
  1408. ClientData clientData;
  1409. Tcl_Interp *interp;
  1410. int argc;
  1411. char **argv;
  1412. {
  1413.     static int trace_level = 0;
  1414.     static Tcl_Trace trace_handle;
  1415.  
  1416.     if (argc != 2) {
  1417.         tcl_error("usage: trace level");
  1418.         return(TCL_ERROR);
  1419.     }
  1420.     /* tracing already in effect, undo it */
  1421.     if (trace_level > 0) Tcl_DeleteTrace(interp,trace_handle);
  1422.  
  1423.     /* get and save new trace level */
  1424.     trace_level = atoi(argv[1]);
  1425.     if (trace_level > 0)
  1426.         trace_handle = Tcl_CreateTrace(interp,
  1427.                 trace_level,tcl_tracer,(ClientData)0);
  1428.     return(TCL_OK);
  1429. }
  1430.  
  1431. static char *wait_usage = "usage: wait [-i spawn_id]";
  1432.  
  1433. /*ARGSUSED*/
  1434. static int
  1435. cmdWait(clientData, interp, argc, argv)
  1436. ClientData clientData;
  1437. Tcl_Interp *interp;
  1438. int argc;
  1439. char **argv;
  1440. {
  1441.     int master;
  1442.     static char waitstr[80];   /* should be long enough for two ints */
  1443.     struct f *f;
  1444.     /* if your C compiler bombs here, define NO_PID_T in Makefile */
  1445. #ifdef NO_PID_T
  1446.     int pid = 0;    /* 0 indicates no error occurred (yet) */
  1447. #else
  1448.     pid_t pid = 0;    /* ditto */
  1449. #endif
  1450.     int WaitedP=0;
  1451.     int WaitStatus;
  1452.  
  1453.     argc--; argv++;
  1454.  
  1455.     if (argc == 0) {
  1456.       debuglog("wait(&WaitStatus)");
  1457.       pid = wait(&WaitStatus);
  1458.       debuglog("=> pid: %d, WaitStatus: %d\n", pid, WaitStatus);
  1459.       if (pid != -1) {
  1460.         WaitedP = 1;
  1461.         master = pid_to_fd(pid);
  1462.       }
  1463.       else {
  1464.         sprintf(waitstr,"-1 %d", errno);
  1465.         /* return {pid status} or {-1 errno} */
  1466.         Tcl_Return(interp,waitstr,TCL_STATIC);
  1467.         return(TCL_OK);    
  1468.       }
  1469.     }
  1470.     else if (argc == 0) {
  1471.       if (0 == update_master(&master)) return(TCL_ERROR);
  1472.     }
  1473.     else if (streq(argv[0],"-i")) {
  1474.       if (argc != 2) {
  1475.         tcl_error(wait_usage);
  1476.         return(TCL_ERROR);
  1477.       }
  1478.       master = atoi(argv[1]);
  1479.     } else {
  1480.       tcl_error(wait_usage);
  1481.       return(TCL_ERROR);
  1482.     }
  1483.     if (!(f = fd_to_f(master,"wait"))) return(TCL_ERROR);
  1484.  
  1485.     if (!(f->flags & FD_SYSWAITED)) {
  1486. #ifdef NOWAITPID
  1487.         int status;
  1488.         for (;;) {
  1489.             int i;
  1490.  
  1491.             if (WaitedP) { status = WaitStatus; WaitedP = 0; }
  1492.             else {
  1493.               debuglog("wait(&status)");
  1494.               pid = wait(&status);
  1495.               debuglog("=> pid: %d, status: %d\n", pid, status);
  1496.             }
  1497.             if (pid == f->pid) break;
  1498.             /* oops, wrong pid */
  1499.             for (i=0;i<=fd_max;i++) {
  1500.                 if (fs[i].pid == pid) break;
  1501.             }
  1502.             if (i>fd_max) {
  1503.                 debuglog("wait found unknown pid %d\r\n",pid);
  1504.                 continue;    /* drop on floor */
  1505.             }
  1506.             debuglog("cmdWait(%d) (f->flags: %x |= FD_SYSWAITED(%x)) => %x\n", i, fs[i].flags, FD_SYSWAITED, fs[i].flags|FD_SYSWAITED);
  1507.             fs[i].flags |= FD_SYSWAITED;
  1508.             fs[i].wait = status;
  1509.             if (SetSessionFromFD(i)) DeleteSession();    /* MDW: Mon Jun 22 13:21:20 1992 */
  1510.         }
  1511.         f->wait = status;
  1512. #else
  1513.         if (WaitedP) f->wait = WaitStatus;
  1514.         else {
  1515.           debuglog("waitpid(%d,&f->wait,0)", f->pid);
  1516.           pid = waitpid(f->pid,&f->wait,0);
  1517.           debuglog("=> pid: %d, status: %d\n", pid, f->wait);
  1518.         }
  1519. #endif
  1520.         if (pid == f->pid) {
  1521.           debuglog("cmdWait(%d) (f->flags: %x |= FD_SYSWAITED(%x)) => %x\n", f-fs, f->flags, FD_SYSWAITED, f->flags|FD_SYSWAITED);
  1522.           f->flags |= FD_SYSWAITED;
  1523.         }
  1524.     }
  1525.     if (f->flags & FD_CLOSED) {
  1526.       debuglog("cmdWait(%d) f->flags: %x = 0\n", f-fs,f->flags);
  1527.       f->flags = 0;
  1528.       if (SetSessionFromFD(f-fs)) DeleteSession();    /* MDW: Mon Jun 22 13:21:24 1992 */
  1529.     }
  1530.     if (pid == f->pid) {
  1531.       debuglog("cmdWait(%d) (f->flags: %x |= FD_USERWAITED(%x)) => %x\n", f-fs, f->flags, FD_USERWAITED, f->flags|FD_USERWAITED);
  1532.       f->flags |= FD_USERWAITED;
  1533.     }
  1534. #ifndef WEXITSTATUS
  1535. #define WEXITSTATUS(x) x
  1536. #endif
  1537.     /* non-portable assumption that pid_t is an int */
  1538.     sprintf(waitstr,"%d %d",((pid <= 0)?-1:pid),((pid <= 0)?errno:WEXITSTATUS(f->wait)));
  1539.     /* return {pid status} or {-1 errno} */
  1540.     Tcl_Return(interp,waitstr,TCL_STATIC);
  1541.     return(TCL_OK);
  1542. }
  1543.  
  1544. /*ARGSUSED*/
  1545. static int
  1546. cmdFork(clientData, interp, argc, argv)
  1547. ClientData clientData;
  1548. Tcl_Interp *interp;
  1549. int argc;
  1550. char **argv;
  1551. {
  1552.     static char pidstr[32];
  1553.     int pid;
  1554.  
  1555.     if (argc > 1) {
  1556.         tcl_error("usage: fork");
  1557.         return(TCL_ERROR);
  1558.     }
  1559.  
  1560.     if (0 == (pid = fork())) forked = TRUE;
  1561.  
  1562.     sprintf(pidstr,"%d",pid);
  1563.     debuglog("fork: returns {%s}\r\n",pidstr);
  1564.     Tcl_Return(interp,pidstr,TCL_STATIC);
  1565.     return(TCL_OK);
  1566. }
  1567.  
  1568. /*ARGSUSED*/
  1569. static int
  1570. cmdDisconnect(clientData, interp, argc, argv)
  1571. ClientData clientData;
  1572. Tcl_Interp *interp;
  1573. int argc;
  1574. char **argv;
  1575. {
  1576.     int ttyfd;
  1577.  
  1578.     if (argc > 1) {
  1579.         tcl_error("usage: disconnect");
  1580.         return(TCL_ERROR);
  1581.     }
  1582.  
  1583.     if (disconnected) {
  1584.         tcl_error("disconnect: already disconnected");
  1585.         return(TCL_ERROR);
  1586.     }
  1587.     if (!forked) {
  1588.         tcl_error("disconnect: can only disconnect child process");
  1589.         return(TCL_ERROR);
  1590.     }
  1591.     disconnected = TRUE;
  1592.  
  1593.     /* ignore hangup signals generated by testing ptys in getptymaster */
  1594.     /* and other places */
  1595.     Signal(SIGHUP,SIG_IGN);
  1596.  
  1597.     /* freopen(,,stdin) prevents confusion between send/expect_user */
  1598.     /* accidentally mapping to a real spawned process after a disconnect */
  1599.     /* A better solution might be to put code in the _user routines to */
  1600.     /* check that a disconnect has occured. */
  1601.     /* freopen(,,stderr) saves error checking in error/log routines. */
  1602.     freopen("/dev/null","r",stdin);
  1603.     freopen("/dev/null","w",stdout);
  1604.     freopen("/dev/null","w",stderr);
  1605.  
  1606. #ifdef SYSV3
  1607.     /* put process in our own pgrp, and lose controlling terminal */
  1608. #ifdef sysV88
  1609.     /* With setpgrp first, child ends up with closed stdio */
  1610.     /* according to Dave Schmitt <daves@techmpc.csg.gss.mot.com> */
  1611.     if (fork()) exit(0);
  1612.     setpgrp();
  1613. #else
  1614.     setpgrp();
  1615.     /*Signal(SIGHUP,SIG_IGN); moved out to above */
  1616.     if (fork()) exit(0);    /* first child exits (as per Stevens, */
  1617.     /* UNIX Network Programming, p. 79-80) */
  1618.     /* second child process continues as daemon */
  1619. #endif
  1620. #else /* !SYSV3 */
  1621. #ifdef MIPS_BSD
  1622.     /* required on BSD side of MIPS OS <jmsellen@watdragon.waterloo.edu> */
  1623. #    include <sysv/sys.s>
  1624.     syscall(SYS_setpgrp);
  1625. #endif
  1626.     setpgrp(0,getpid());    /* put process in our own pgrp */
  1627.     ttyfd = open("/dev/tty", O_RDWR);
  1628.     if (ttyfd >= 0) {
  1629.         /* zap controlling terminal if we had one */
  1630.         (void) ioctl(ttyfd, TIOCNOTTY, (char *)0);
  1631.         (void) close(ttyfd);
  1632.     }
  1633. #endif /* SYSV3 */
  1634.     return(TCL_OK);
  1635. }
  1636.  
  1637. /*ARGSUSED*/
  1638. static int
  1639. cmdOverlay(clientData, interp, argc, argv)
  1640. ClientData clientData;
  1641. Tcl_Interp *interp;
  1642. int argc;
  1643. char **argv;
  1644. {
  1645.     int newfd, oldfd;
  1646.     int dash_name = 0;
  1647.     char *command;
  1648.  
  1649.     argc--; argv++;
  1650.     while (argc) {
  1651.         if (*argv[0] != '-') break;    /* not a flag */
  1652.         if (streq(*argv,"-")) {        /* - by itself */
  1653.             argc--; argv++;
  1654.             dash_name = 1;
  1655.             continue;
  1656.         }
  1657.         newfd = atoi(argv[0]+1);
  1658.         argc--; argv++;
  1659.         if (argc == 0) {
  1660.             tcl_error("overlay -i requires additional argument");
  1661.             return(TCL_ERROR);
  1662.         }
  1663.         oldfd = atoi(argv[0]);
  1664.         argc--; argv++;
  1665.         debuglog("overlay: mapping fd %d to %d\r\n",oldfd,newfd);
  1666.         if (oldfd != newfd) (void) dup2(oldfd,newfd);
  1667.         else debuglog("warning: overlay: old fd == new fd (%d)\r\n",oldfd);
  1668.     }
  1669.     if (argc == 0) {
  1670.         tcl_error("overlay: need program name");
  1671.         return(TCL_ERROR);
  1672.     }
  1673.     flush_streams();
  1674.     command = argv[0];
  1675.     if (dash_name) {
  1676.         argv[0] = (char *)malloc(1+strlen(command));
  1677.         /* check for malloc failure */
  1678.         sprintf(argv[0],"-%s",command);
  1679.     }
  1680.  
  1681.     Signal(SIGINT, SIG_DFL);
  1682.     Signal(SIGQUIT, SIG_DFL);
  1683.         (void) execvp(command,argv);
  1684.     errorlog("execvp(%s): %s\r\n",argv[0],sys_errlist[errno]);
  1685.     exit(-1);
  1686.     /*NOTREACHED*/
  1687. }
  1688.  
  1689. #if 0
  1690. /*ARGSUSED*/
  1691. int
  1692. cmdReady(clientData, interp, argc, argv)
  1693. ClientData clientData;
  1694. Tcl_Interp *interp;
  1695. int argc;
  1696. char **argv;
  1697. {
  1698.     char num[4];    /* can hold up to "999 " */
  1699.     char buf[1024];    /* can easily hold 256 spawn_ids! */
  1700.     int i, j;
  1701.     int *masters, *masters2;
  1702.     double timeout = get_timeout();
  1703.     struct timeval tv_struct, *tv=&tv_struct;
  1704.  
  1705.     if (argc < 2) {
  1706.         tcl_error("usage: ready spawn_id1 [spawn_id2 ...]");
  1707.         return(TCL_ERROR);
  1708.     }
  1709.  
  1710.     if (0 == (masters = (int *)malloc((argc-1)*sizeof(int)))) {
  1711.         tcl_error("malloc(%d spawn_id's)",(argc-1));
  1712.         return(TCL_ERROR);
  1713.     }
  1714.     if (0 == (masters2 = (int *)malloc((argc-1)*sizeof(int)))) {
  1715.         tcl_error("malloc(%d spawn_id's)",(argc-1));
  1716.         return(TCL_ERROR);
  1717.     }
  1718.  
  1719.     for (i=1;i<argc;i++) {
  1720.         j = atoi(argv[i]);
  1721.         if (!fd_to_f(j,"ready")) {
  1722.             free(masters);
  1723.             return(TCL_ERROR);
  1724.         }
  1725.         masters[i-1] = j;
  1726.     }
  1727.     j = i-1;
  1728.     if (timeout == TIME_INFINITY) tv=NULL;
  1729.     else DoubleToTimeval(timeout,tv);
  1730.     if (TCL_ERROR == ready(masters,i-1,masters2,&j,tv))
  1731.         return(TCL_ERROR);
  1732.  
  1733.     /* pack result back into out-array */
  1734.     buf[0] = '\0';
  1735.     for (i=0;i<j;i++) {
  1736.         sprintf(num,"%d ",masters2[i]); /* note extra blank */
  1737.         strcat(buf,num);
  1738.     }
  1739.     free(masters); free(masters2);
  1740.     Tcl_Return(interp,buf,TCL_VOLATILE);
  1741.     return(TCL_OK);
  1742. }
  1743. #endif
  1744.  
  1745. /*ARGSUSED*/
  1746. int
  1747. cmdInterpreter(clientData, interp, argc, argv)
  1748. ClientData clientData;
  1749. Tcl_Interp *interp;
  1750. int argc;
  1751. char **argv;
  1752. {
  1753.       extern int escape();
  1754.     if (argc != 1) {
  1755.         tcl_error("interpreter: no arguments allowed");
  1756.         return(TCL_ERROR);
  1757.     }
  1758.  
  1759.     FuncallWithoutCurses(escape, 1, interp);
  1760.     /* errors and ok, are caught by escape() and discarded */
  1761.     /* the only thing that actually comes out of escape are */
  1762.     /* RETURN, BREAK, and CONTINUE which we all translate to OK */
  1763.     return(TCL_OK);
  1764. }
  1765.  
  1766. /* this command supercede's Tcl's builtin CONTINUE command */
  1767. /*ARGSUSED*/
  1768. int
  1769. cmdContinueExpect(clientData, interp, argc, argv)
  1770. ClientData clientData;
  1771. Tcl_Interp *interp;
  1772. int argc;
  1773. char **argv;
  1774. {
  1775.     if (argc == 1) return(TCL_CONTINUE);
  1776.     else if (argc == 2) {
  1777.         if (streq(argv[1],"-expect")) {
  1778.             return(TCL_CONTINUE_EXPECT);
  1779.         }
  1780.     }
  1781.     tcl_error("usage: continue [-expect]\n");
  1782.     return(TCL_ERROR);
  1783. }
  1784.  
  1785. /* this command supercede's Tcl's builtin RETURN command */
  1786. /*ARGSUSED*/
  1787. int
  1788. cmdReturnInter(clientData, interp, argc, argv)
  1789. ClientData clientData;
  1790. Tcl_Interp *interp;
  1791. int argc;
  1792. char **argv;
  1793. {
  1794.     int rc = TCL_RETURN;
  1795.  
  1796.     if (argc == 1) return(TCL_RETURN);
  1797.     if (argc > 3) {
  1798.         tcl_error("usage: return [-tcl] [value]\n");
  1799.         return(TCL_ERROR);
  1800.     }
  1801.  
  1802.     if (streq(argv[1],"-tcl")) rc = TCL_RETURN_TCL;
  1803.  
  1804.     if (!(rc == TCL_RETURN_TCL && argc == 2)) {
  1805.         /* if either return "-tcl expr" or "expr", get the expr */
  1806.         Tcl_SetResult(interp,argv[1],TCL_VOLATILE);
  1807.     }
  1808.  
  1809.     return(rc);
  1810. }
  1811.  
  1812. #define deleteProc (void (*)())0
  1813.  
  1814. extern struct expect_special before, after;
  1815.  
  1816. void
  1817. init_cmd_interpreter()
  1818. {
  1819.     extern int cmdInteract();
  1820. #if 0
  1821.     extern int cmdReady();
  1822. #endif
  1823.  
  1824.     extern int cmdExpectVersion();
  1825.     extern int cmdExpect();
  1826.     extern int cmdExpectGlobal();
  1827.     extern int cmdMatchMax();
  1828.     extern int cmdTrap();
  1829.  
  1830.     interp = Tcl_CreateInterp();
  1831.     /* call by explicitly, so user can put history info in init prompt */
  1832.     Tcl_InitHistory(interp);
  1833.  
  1834.     init_switch_interpreter(interp);    /* MDW: Mon Jun 22 13:24:59 1992 */
  1835.  
  1836.     Tcl_CreateCommand(interp,"close",
  1837.         cmdClose,(ClientData)0,deleteProc);
  1838.     Tcl_CreateCommand(interp,"debug",
  1839.         cmdDebug,(ClientData)0,deleteProc);
  1840.     Tcl_CreateCommand(interp,"disconnect",
  1841.         cmdDisconnect,(ClientData)0,deleteProc);
  1842.     Tcl_CreateCommand(interp,"exit",
  1843.         cmdExit,(ClientData)0,deleteProc);
  1844.     Tcl_CreateCommand(interp,"expect",
  1845.         cmdExpect,(ClientData)&expectCD_process,deleteProc);
  1846.     Tcl_CreateCommand(interp,"expect_after",
  1847.         cmdExpectGlobal,(ClientData)&after,deleteProc);
  1848.     Tcl_CreateCommand(interp,"expect_before",
  1849.         cmdExpectGlobal,(ClientData)&before,deleteProc);
  1850.     Tcl_CreateCommand(interp,"continue",
  1851.         cmdContinueExpect,(ClientData)0,deleteProc);
  1852.     Tcl_CreateCommand(interp,"expect_user",
  1853.         cmdExpect,(ClientData)&expectCD_user,deleteProc);
  1854.     Tcl_CreateCommand(interp,"expect_version",
  1855.         cmdExpectVersion,(ClientData)0,deleteProc);
  1856.     Tcl_CreateCommand(interp,"fork",
  1857.         cmdFork,(ClientData)0,deleteProc);
  1858.     Tcl_CreateCommand(interp,"interact",
  1859.         cmdInteract,(ClientData)0,deleteProc);
  1860.     Tcl_CreateCommand(interp,"interpreter",
  1861.         cmdInterpreter,(ClientData)0,deleteProc);
  1862.     Tcl_CreateCommand(interp,"log_file",
  1863.         cmdLogFile,(ClientData)0,deleteProc);
  1864.     Tcl_CreateCommand(interp,"log_user",
  1865.         cmdLogUser,(ClientData)0,deleteProc);
  1866.     Tcl_CreateCommand(interp,"match_max",
  1867.         cmdMatchMax,(ClientData)0,deleteProc);
  1868.     Tcl_CreateCommand(interp,"overlay",
  1869.         cmdOverlay,(ClientData)0,deleteProc);
  1870. #if 0
  1871.     Tcl_CreateCommand(interp,"ready",
  1872.         cmdReady,(ClientData)0,deleteProc);
  1873. #endif
  1874.     Tcl_CreateCommand(interp,"return",
  1875.         cmdReturnInter,(ClientData)0,deleteProc);
  1876.     Tcl_CreateCommand(interp,"send",
  1877.         cmdSend,(ClientData)&sendCD_proc,deleteProc);
  1878.     Tcl_CreateCommand(interp,"send_error",
  1879.         cmdSend,(ClientData)&sendCD_error,deleteProc);
  1880.     Tcl_CreateCommand(interp,"send_user",
  1881.         cmdSend,(ClientData)&sendCD_user,deleteProc);
  1882.     Tcl_CreateCommand(interp,"spawn",
  1883.         cmdSpawn,(ClientData)0,deleteProc);
  1884.     Tcl_CreateCommand(interp,"strace",
  1885.         cmdTrace,(ClientData)0,deleteProc);
  1886.     Tcl_CreateCommand(interp,"system",
  1887.         cmdSystem,(ClientData)0,deleteProc);
  1888.     Tcl_CreateCommand(interp,"trap",
  1889.         cmdTrap,(ClientData)0,deleteProc);
  1890.     Tcl_CreateCommand(interp,"wait",
  1891.         cmdWait,(ClientData)0,deleteProc);
  1892. }
  1893.